

#ifndef __IRR_AABBOX_3D_H_INCLUDED__
#define __IRR_AABBOX_3D_H_INCLUDED__

#include "irrMath.h"
#include "plane3d.h"
#include "line3d.h"

namespace irr
{
    namespace core
    {

        //! Axis aligned bounding box in 3d dimensional space.
        /** Has some useful methods used with occlusion culling or clipping.
        */
        template <class T>
        class aabbox3d
        {
        public:

            // Constructors

            aabbox3d(): MinEdge(-1,-1,-1), MaxEdge(1,1,1) {};
            aabbox3d(const vector3d<T>& min, const vector3d<T>& max): MinEdge(min), MaxEdge(max) {};
            aabbox3d(const vector3d<T>& init): MinEdge(init), MaxEdge(init) {};
            aabbox3d(T minx, T miny, T minz, T maxx, T maxy, T maxz): MinEdge(minx, miny, minz), MaxEdge(maxx, maxy, maxz) {};

            // operators

            inline bool operator==(const aabbox3d<T>& other) const { return (MinEdge == other.MinEdge && other.MaxEdge == MaxEdge);};
            inline bool operator!=(const aabbox3d<T>& other) const { return !(MinEdge == other.MinEdge && other.MaxEdge == MaxEdge);};

            // functions

            //! Adds a point to the bounding box, causing it to grow bigger, 
            //! if point is outside of the box
            //! \param p: Point to add into the box.
            void addInternalPoint(const vector3d<T>& p)
            {
                addInternalPoint(p.X, p.Y, p.Z);
            }

            //! Adds an other bounding box to the bounding box, causing it to grow bigger,
            //! if the box is outside of the box
            //! \param b: Other bounding box to add into this box.
            void addInternalBox(const aabbox3d<T>& b)
            {
                addInternalPoint(b.MaxEdge);
                addInternalPoint(b.MinEdge);
            }

            //! Resets the bounding box.
            void reset(T x, T y, T z)
            {
                MaxEdge.set(x,y,z);
                MinEdge = MaxEdge;
            }

            //! Resets the bounding box.
            void reset(const aabbox3d<T>& initValue)
            {
                *this = initValue;
            }

            //! Resets the bounding box.
            void reset(const vector3d<T>& initValue)
            {
                MaxEdge = initValue;
                MinEdge = initValue;
            }

            //! Adds a point to the bounding box, causing it to grow bigger, 
            //! if point is outside of the box.
            //! \param x: X Coordinate of the point to add to this box.
            //! \param y: Y Coordinate of the point to add to this box.
            //! \param z: Z Coordinate of the point to add to this box.
            void addInternalPoint(T x, T y, T z)
            {
                if (x>MaxEdge.X) MaxEdge.X = x;
                if (y>MaxEdge.Y) MaxEdge.Y = y;
                if (z>MaxEdge.Z) MaxEdge.Z = z;

                if (x<MinEdge.X) MinEdge.X = x;
                if (y<MinEdge.Y) MinEdge.Y = y;
                if (z<MinEdge.Z) MinEdge.Z = z;
            }

            //! Determinates if a point is within this box.
            //! \param p: Point to check.
            //! \return Returns true if the point is withing the box, and false if it is not.
            bool isPointInside(const vector3d<T>& p) const
            {
                return (	p.X >= MinEdge.X && p.X <= MaxEdge.X &&
                    p.Y >= MinEdge.Y && p.Y <= MaxEdge.Y &&
                    p.Z >= MinEdge.Z && p.Z <= MaxEdge.Z);
            };

            //! Determinates if a point is within this box and its borders.
            //! \param p: Point to check.
            //! \return Returns true if the point is withing the box, and false if it is not.
            bool isPointTotalInside(const vector3d<T>& p) const
            {
                return (	p.X > MinEdge.X && p.X < MaxEdge.X &&
                    p.Y > MinEdge.Y && p.Y < MaxEdge.Y &&
                    p.Z > MinEdge.Z && p.Z < MaxEdge.Z);
            };

            //! Determinates if the box intersects with an other box.
            //! \param other: Other box to check a intersection with.
            //! \return Returns true if there is a intersection with the other box, 
            //! otherwise false.
            bool intersectsWithBox(const aabbox3d<T>& other) const
            {
                return (MinEdge <= other.MaxEdge && MaxEdge >= other.MinEdge);
            }

            //! Tests if the box intersects with a line
            //! \param line: Line to test intersection with.
            //! \return Returns true if there is an intersection and false if not.
            bool intersectsWithLine(const line3d<T>& line) const
            {
                return intersectsWithLine(line.getMiddle(), line.getVector().normalize(), 
                    (T)(line.getLength() * 0.5));
            }

            //! Tests if the box intersects with a line
            //! \return Returns true if there is an intersection and false if not.
            bool intersectsWithLine(const vector3d<T>& linemiddle, 
                const vector3d<T>& linevect,
                T halflength) const
            {
                const vector3d<T> e = (MaxEdge - MinEdge) * (T)0.5;
                const vector3d<T> t = (MinEdge + e) - linemiddle;
                float r;

                if ((fabs(t.X) > e.X + halflength * fabs(linevect.X)) || 
                    (fabs(t.Y) > e.Y + halflength * fabs(linevect.Y)) ||
                    (fabs(t.Z) > e.Z + halflength * fabs(linevect.Z)) )
                    return false;

                r = e.Y * (T)fabs(linevect.Z) + e.Z * (T)fabs(linevect.Y);
                if (fabs(t.Y*linevect.Z - t.Z*linevect.Y) > r )
                    return false;

                r = e.X * (T)fabs(linevect.Z) + e.Z * (T)fabs(linevect.X);
                if (fabs(t.Z*linevect.X - t.X*linevect.Z) > r )
                    return false;

                r = e.X * (T)fabs(linevect.Y) + e.Y * (T)fabs(linevect.X);
                if (fabs(t.X*linevect.Y - t.Y*linevect.X) > r)
                    return false;

                return true;
            }

            //! Classifies a relation with a plane.
            //! \param plane: Plane to classify relation to.
            //! \return Returns ISREL3D_FRONT if the box is in front of the plane,
            //! ISREL3D_BACK if the box is back of the plane, and
            //! ISREL3D_CLIPPED if is on both sides of the plane.
            EIntersectionRelation3D classifyPlaneRelation(const plane3d<f32>& plane) const
            {
                vector3d<T> nearPoint(MaxEdge);
                vector3d<T> farPoint(MinEdge);

                if (plane.Normal.X > (T)0)
                {
                    nearPoint.X = MinEdge.X;
                    farPoint.X = MaxEdge.X;
                }

                if (plane.Normal.Y > (T)0)
                {
                    nearPoint.Y = MinEdge.Y;
                    farPoint.Y = MaxEdge.Y;
                }

                if (plane.Normal.Z > (T)0)
                {
                    nearPoint.Z = MinEdge.Z;
                    farPoint.Z = MaxEdge.Z;
                }

                if (plane.Normal.dotProduct(nearPoint) + plane.D > (T)0)
                    return ISREL3D_FRONT;

                if (plane.Normal.dotProduct(farPoint) + plane.D > (T)0)
                    return ISREL3D_CLIPPED;

                return ISREL3D_BACK;
            }


            //! returns center of the bounding box
            vector3d<T> getCenter() const
            {
                return (MinEdge + MaxEdge) / 2;
            }

            //! returns extend of the box
            vector3d<T> getExtent() const
            {
                return MaxEdge - MinEdge;
            }


            //! stores all 8 edges of the box into a array
            //! \param edges: Pointer to array of 8 edges
            void getEdges(vector3d<T> *edges) const
            {
                core::vector3df middle = (MinEdge + MaxEdge) / 2;
                core::vector3df diag = middle - MaxEdge;

                /*
                Edges are stored in this way:
                Hey, am I an ascii artist, or what? :) niko.
                /1--------/3
                /  |      / |
                /   |     /  |
                5---------7  |
                |   0- - -| -2
                |  /      |  /
                |/        | /
                4---------6/ 
                */

                edges[0].set(middle.X + diag.X, middle.Y + diag.Y, middle.Z + diag.Z);
                edges[1].set(middle.X + diag.X, middle.Y - diag.Y, middle.Z + diag.Z);
                edges[2].set(middle.X + diag.X, middle.Y + diag.Y, middle.Z - diag.Z);
                edges[3].set(middle.X + diag.X, middle.Y - diag.Y, middle.Z - diag.Z);
                edges[4].set(middle.X - diag.X, middle.Y + diag.Y, middle.Z + diag.Z);
                edges[5].set(middle.X - diag.X, middle.Y - diag.Y, middle.Z + diag.Z);
                edges[6].set(middle.X - diag.X, middle.Y + diag.Y, middle.Z - diag.Z);
                edges[7].set(middle.X - diag.X, middle.Y - diag.Y, middle.Z - diag.Z);
            }


            //! returns if the box is empty, which means that there is
            //! no space within the min and the max edge.
            bool isEmpty() const
            {
                core::vector3df d = MinEdge - MaxEdge;
                if (d.X < 0) d.X = -d.X;
                if (d.Y < 0) d.Y = -d.Y;
                if (d.Z < 0) d.Z = -d.Z;

                return (d.X < ROUNDING_ERROR && 
                    d.Y < ROUNDING_ERROR && 
                    d.Z < ROUNDING_ERROR);
            }

            //! repairs the box, if for example MinEdge and MaxEdge are swapped.
            void repair()
            {
                T t;

                if (MinEdge.X > MaxEdge.X)
                { t=MinEdge.X; MinEdge.X = MaxEdge.X; MaxEdge.X=t; }
                if (MinEdge.Y > MaxEdge.Y)
                { t=MinEdge.Y; MinEdge.Y = MaxEdge.Y; MaxEdge.Y=t; }
                if (MinEdge.Z > MaxEdge.Z)
                { t=MinEdge.Z; MinEdge.Z = MaxEdge.Z; MaxEdge.Z=t; }
            }

            //! Calculates a new interpolated bounding box.
            //! \param other: other box to interpolate between
            //! \param d: value between 0.0f and 1.0f.
            aabbox3d<T> getInterpolated(const aabbox3d<T>& other, f32 d) const
            {
                f32 inv = 1.0f - d;
                return aabbox3d<T>((other.MinEdge*inv) + (MinEdge*d),
                    (other.MaxEdge*inv) + (MaxEdge*d));
            }

            // member variables

            vector3d<T> MinEdge;
            vector3d<T> MaxEdge;
        };

        //! Typedef for a f32 3d bounding box.
        typedef aabbox3d<f32> aabbox3df;
        //! Typedef for an integer 3d bounding box.
        typedef aabbox3d<s32> aabbox3di;


    } // end namespace core
} // end namespace irr

#endif

